package com.write.Quill.data;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Collections;

import junit.framework.Assert;

import android.util.Log;

/**
 * The tag manager keeps track of tags. Each Tag is a unique object. 
 * 
 * @author vbraun
 *
 */
public class TagManager {
	private static final String TAG = "TagManager";
	private LinkedList<WeakReference<TagSet>> allTagSets = 
			new LinkedList<WeakReference<TagSet>>();
	private LinkedList<Tag> allTags = new LinkedList<Tag>();
	private LinkedList<Tag> allTagsByCount = new LinkedList<Tag>();

	
	
	/**
	 * A single Tag. Use newTag() to construct them.
	 * 
	 * @author vbraun
	 *
	 */
	public class Tag {
		private String name;
		protected int count = 0;
		protected boolean autogenerated = false;
		protected long ctime;
		
		private Tag(String tagName) {
			name = new String(tagName);
			ctime = System.currentTimeMillis();
		}
		
		public String toString() {
			return name;
		}
		
		public void write_to_stream(DataOutputStream out) throws IOException {
			out.writeInt(1);  // protocol #1
			out.writeUTF(name);
			out.writeBoolean(autogenerated);
			out.writeLong(ctime);
			out.writeLong(0); // reserved
		}

		private Tag(DataInputStream in) throws IOException {
			int version = in.readInt();
			if (version != 1)
				throw new IOException("Unknown version!");
			name = in.readUTF();
			autogenerated = in.readBoolean();
			ctime = in.readLong();
			in.readLong(); // reserved
		}
		
		public void rename(String name) {
			renameTag(this, name);
		}
	}
	
	/**
	 * A TagSet is a collection of Tags
	 * Code should never hold on to individual Tags, only to TagSets.
	 *  
	 * @author vbraun
	 *
	 */
	public class TagSet {
		protected LinkedList<Tag> tags = new LinkedList<Tag>();
		
		// use TagManager.newTagSet() to construct a TagSet
		private TagSet() {
		}
		
		public TagSet copy() {
			TagSet ts = newTagSet();
			ts.tags.addAll(ts.tags);
			return ts;
		}
		
		public boolean contains(Tag tag) {
			return tags.contains(tag);
		}
		
		public boolean add(Tag tag) {
			if (!tags.contains(tag)) {
				tags.add(tag);
				return true;
			}
			return false;
		}
		
		public boolean add(TagSet tagSet) {
			ListIterator<Tag> iter = tagSet.tagIterator();
			boolean rc = false;
			while (iter.hasNext()) {
				Tag t = iter.next();
				boolean tIsNew = add(t);
				rc = rc || tIsNew; // beware of short-circuit
			}
			return rc;
		}
		
		public boolean remove(Tag tag) {
			return tags.remove(tag);
		}
		
		public ListIterator<Tag> tagIterator() {
			return tags.listIterator();
		}
		
		public LinkedList<Tag> allTags() {
			return allTags;
		}
		
		public int size() {
			return tags.size();
		}
		
		public void write_to_stream(DataOutputStream out) throws IOException {
			out.writeInt(1);  // protocol #1
			out.writeInt(tags.size());
			// Log.d(TAG, "TagSet wrote n = "+tags.size());
			ListIterator<Tag> iter = tags.listIterator();
			while (iter.hasNext()) {
				Tag t = iter.next();
				t.write_to_stream(out);
			}
			out.writeInt(0); // reserved1
			out.writeInt(0); // reserved2
		}

		public TagSet(DataInputStream in) throws IOException {
			int version = in.readInt();
			if (version != 1)
				throw new IOException("Unknown version!");
			int n = in.readInt();
			// Log.d(TAG, "TagSet read n = "+n);
			for (int i=0; i<n; i++) {
				Tag tag = new Tag(in);
				Tag existing_tag = findTag(tag.toString());
				if (existing_tag != null) {
					add(existing_tag);
				} else {
					add(tag);
					allTags.add(tag);
					allTagsByCount.add(tag);
				}
			}
			in.readInt();  // reserved1
			in.readInt();  // reserved2
		}
	}
	

	/**
	 * This static method is the only way to construct new TagSets.
	 * 
	 * @return a new TagSet
	 */
	public TagSet newTagSet() {
		TagSet ts = new TagSet();
		allTagSets.add(new WeakReference<TagSet>(ts));
		// Log.d(TAG, "size = "+allTagSets.size()+" "+ts);
		return ts;
	}
	
	/**
	 * This static method is the only wa to construct a new Tag
	 * 
	 * @param name the name of the new tag.
	 * @return a Tag with the given name. Might be an already 
	 * 		   existing Tag if it carries the same name.
	 */
	public Tag newTag(String name) {
		Tag t = findTag(name);
		if (t == null) {
			t = new Tag(name);
			allTags.add(t);
			allTagsByCount.add(t);
		}
		// Log.d(TAG, "Created new tag "+name+" "+allTags.size());
		return t;
	}
	
	public TagSet loadTagSet(DataInputStream in) throws IOException {
		TagSet ts = new TagSet(in);
		allTagSets.add(new WeakReference<TagSet>(ts));
		// Log.d(TAG, "size = "+allTagSets.size()+" "+ts);
		return ts;
	}
	
	/**
	 * Return the tag with matching name ignoring the specified tag
	 * 
	 * @param name
	 * @param tag
	 * @return null if no such tag exists
	 */
	public Tag findTagExcept(String name, Tag tag) {
		ListIterator<Tag> iter = allTags.listIterator();
		while (iter.hasNext()) {
			Tag t = iter.next();
			if (name.equalsIgnoreCase(t.name) && t != tag)
				return t;
		}
		return null;
	}

	
	/**
	 * Find a tag with given name (case insensitively)
	 *  
	 * @param name The name of the tag you are looking for
	 * @return null if no such tag exists.
	 */
	public Tag findTag(String name) {
		return findTagExcept(name, null);
	}
		
	public Tag get(int position) {
		return allTags.get(position);
	}
	
	/**
	 * Sort the tags. 
	 */
	public void sort() {		
		Assert.assertTrue(allTags.size() == allTagsByCount.size());
		expungeTagSets();
		countTags();
		// Collections.sort(allTags, new CompareCount());
		Collections.sort(allTags, new CompareLexicographic());
		Collections.sort(allTagsByCount, new CompareCount());
	}
	
	public class CompareCount implements Comparator<Tag> {
		@Override
		public int compare(Tag t0, Tag t1) {
			int x = t0.count;
			int y = t1.count;
			if(x > y) {
				return -1;
			} else if(x == y) {
				return 0;
			} else {
				return 1;
			}
		}
	}

	public class CompareLexicographic implements Comparator<Tag> {
		@Override
		public int compare(Tag t0, Tag t1) {
			return t0.name.compareTo(t1.name);
		}
	}

	/**
	 * remove stale TagSets
	 */
	private void expungeTagSets() {
		ListIterator<WeakReference<TagSet>> iter = allTagSets.listIterator();
		while (iter.hasNext())
			if (iter.next().get() == null)
				iter.remove();
 	}
	
	/**
	 * remove unused Tags
	 */
	public void expungeTags() {
		countTags();
		ListIterator<Tag> tag_iter = allTags.listIterator();
		while (tag_iter.hasNext()) {
			Tag t = tag_iter.next();
			if (t.count == 0) {
				tag_iter.remove();
				allTagsByCount.remove(t);
			}
		}
	}
	
	/**
	 * update tag counts
	 */
	private void countTags() {
		ListIterator<Tag> tag_iter = allTags.listIterator();
		while (tag_iter.hasNext())
			tag_iter.next().count = 0;
		ListIterator<WeakReference<TagSet>> tagSet_iter = allTagSets.listIterator();
		while (tagSet_iter.hasNext()) {
			TagSet ts = tagSet_iter.next().get();
			if (ts == null) continue;
 			tag_iter = ts.tagIterator();
			while (tag_iter.hasNext())
				tag_iter.next().count += 1;
		}	
	}
	
	/**
	 * rename the tag, and merge it with like-named tags if necessary
	 *	
	 * @param tag the tag you want to rename
	 * @param name the new name for the tag
	 */
	public void renameTag(Tag tag, String name) {
		if (name.isEmpty()) 
			deleteTag(tag);
		tag.name = name;
		// merge with other tag of the same name, if there is one
		Tag other = findTagExcept(name, tag);
		Log.d(TAG, "other = "+other+" "+allTagSets.size());
		if (other == null) 
			return; 
		ListIterator<WeakReference<TagSet>> tagSet_iter = allTagSets.listIterator();
		while (tagSet_iter.hasNext()) {
			TagSet ts = tagSet_iter.next().get();
			if (ts == null) continue;
			boolean removed = ts.remove(other);
			if (removed && !ts.contains(tag))
				ts.add(tag);
		}
		allTags.remove(other);
		allTagsByCount.remove(other);

		tagSet_iter = allTagSets.listIterator();
		while (tagSet_iter.hasNext()) {
			TagSet ts = tagSet_iter.next().get();
			if (ts == null) continue;
			ListIterator<Tag> tag_iter = ts.tagIterator();
			while (tag_iter.hasNext()) 
				Assert.assertTrue(allTags.contains(tag_iter.next()));
			Assert.assertFalse(ts.contains(other));
		}
					
	}
	
	/**
	 * delete the given tag
	 * 
	 * @param tag
	 */
	public void deleteTag(Tag tag) {
		allTags.remove(tag);
		allTagsByCount.remove(tag);
		ListIterator<WeakReference<TagSet>> tagSet_iter = allTagSets.listIterator();
		while (tagSet_iter.hasNext()) {
			TagSet ts = tagSet_iter.next().get();
			if (ts == null) continue;
			ts.remove(tag);
		}
	}
}
